// package pcapraw 通过 libpcap 发射 raw 包到网络接口
package pcapraw

import (
	"fmt"
	"net"
	"time"

	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
	"github.com/google/gopacket/pcap"
	"github.com/pkg/errors"
)

type pRaw struct {
	h              *pcap.Handle
	pcapRecvPktsCh chan gopacket.Packet
}

// New 创建 PcapRaw 实例
func New(localIP net.IP, ethtype uint16, bpfExpr string) (*pRaw, error) {
	bpf_ethtype := fmt.Sprintf("ether proto %#x", ethtype)
	if bpfExpr == "" {
		bpfExpr = bpf_ethtype
	} else {
		bpfExpr = bpf_ethtype + " and " + bpfExpr
	}

	pcapIfs, err := pcap.FindAllDevs()
	if err != nil {
		return nil, err
	}

	for _, pIf := range pcapIfs {
		for _, addr := range pIf.Addresses {
			if addr.IP.Equal(localIP) {
				pHandle, err := pcap.OpenLive(pIf.Name, 1024, true, time.Second)
				if err != nil {
					return nil, errors.Wrap(err, "pcap.OpenLive (libpcap required)")
				}

				pHandle.SetDirection(pcap.DirectionIn)
				if err := pHandle.SetBPFFilter(bpfExpr); err != nil {
					return nil, err
				}

				praw := &pRaw{h: pHandle}
				praw.newPacketSource()
				return praw, nil
			}
		}
	}

	return nil, errors.New("no such network interface")
}

// ReadGoPkt 读取一个数据包，如果在n秒内没有读取到数据则返回nil
func (p *pRaw) ReadGoPkt(timeout time.Duration) (gopacket.Packet, error) {
	select {
	case <-time.After(timeout):
		return nil, nil
	case pkt := <-p.pcapRecvPktsCh:
		return pkt, nil
	}
}

// WriteLayers 向pcap句柄注入一个报文
func (p *pRaw) WriteLayers(layers []gopacket.SerializableLayer) error {
	data, err := serializeLayers(layers)
	if err != nil {
		return err
	}
	return p.WriteRaw(data)
}

// Write 向pcap句柄写入raw数据包
func (p *pRaw) WriteRaw(data []byte) error {
	return p.h.WritePacketData(data)
}

// Close 关闭 pcap 句柄
func (p *pRaw) Close() {
	p.h.Close()
}

// newPacketSource
func (p *pRaw) newPacketSource() {
	_ = p.h.SetLinkType(layers.LinkTypeEthernet)

	source := gopacket.NewPacketSource(p.h, p.h.LinkType())
	source.NoCopy = true

	p.pcapRecvPktsCh = source.Packets()
}
